package handler

import (
	"fmt"
	"reflect"
	"strconv"
	"strings"

	"go.uber.org/zap"
	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/reflect/protoreflect"
	"google.golang.org/protobuf/reflect/protoregistry"
)

func init() {
	handlerMap = make(map[int32]*PlayerCmdInvoker)
}

var handlerMap map[int32]*PlayerCmdInvoker

func Register(protoController interface{}) {
	rType := reflect.TypeOf(protoController)
	numField := rType.Elem().NumField()

	for i := 0; i < numField; i++ {
		field := rType.Elem().Field(i)
		tagDescriptor := field.Tag.Get("PlayerCmd")
		if tagDescriptor == "" {
			continue
		}
		defer func() {
			if err := recover(); err != nil {
				//TODO err的类型怎么限定
				zap.L().Error("协助处理器注册失败", zap.String("className", rType.String()), zap.Any("error", err))
			}
		}()
		protoTag := parseProtoTag(tagDescriptor)
		method, exist := rType.MethodByName(protoTag.method)
		if !exist {
			panic(fmt.Sprintf("%T中未找到可导出（首字母大写）的方法%v\n", protoController, protoTag.method))
		}

		// fmt.Println("protoreflect.FullName(protoTag.enum)", protoreflect.FullName(protoTag.enum))
		protoEnum, err := protoregistry.GlobalTypes.FindEnumByName(protoreflect.FullName(protoTag.enum))
		if err != nil {
			panic(fmt.Sprintf("ProtoBuf中未找到全名为%v的枚举\n", protoTag.enum))
		}

		// fmt.Println("protoEnum", protoEnum)
		cmdValue := protoEnum.Descriptor().Values().ByName(protoreflect.Name(protoTag.enumName))
		if cmdValue == nil {
			panic(fmt.Sprintf("枚举%v中未找到名字为%v的值\n", protoTag.enum, protoTag.enumName))
		}
		cmd := int32(cmdValue.Number())
		_, duplicate := handlerMap[cmd]
		if duplicate {
			panic(fmt.Sprintf("协议号%d重复注册", cmd))
		}

		handler := &PlayerCmdInvoker{
			cmd:       cmd,
			needLogin: protoTag.needLogin,
			invoker:   protoController,
			method:    method,
			//方法的参数分别是：0是Handler的指针，1是通信的上下文，2是协议类
			protoStruct: method.Type.In(2).Elem(),
		}
		handlerMap[handler.cmd] = handler
		// fmt.Println(handler.cmd, handler, protoController)
	}
}

// proto的处理函数结构
type PlayerCmdInvoker struct {
	cmd         int32
	needLogin   bool
	invoker     interface{}
	method      reflect.Method
	protoStruct reflect.Type
}

type IPlayerCmdMessage interface {
	GetPlayerContext() IPlayerContext
	GetMessageId() int32
	GetCmd() int32
	GetData() []byte
	OnException(err error, request protoreflect.ProtoMessage)
	OnResponse(response interface{})
}

func Invoke(message IPlayerCmdMessage) {
	invoker, ok := handlerMap[message.GetCmd()]
	if !ok {
		zap.L().Info("未找到请求编号的处理器", zap.Int32("cmd", message.GetCmd()))
		message.OnException(nil, nil)
		return
	}
	structValue := reflect.New(invoker.protoStruct)
	request := structValue.Interface().(protoreflect.ProtoMessage)
	err := proto.Unmarshal(message.GetData(), request)
	if err != nil {
		zap.L().Error("消息解释失败", zap.Int32("cmd", message.GetCmd()), zap.Any("request", request), zap.Any("error", err))
		message.OnException(fmt.Errorf("message parse fail"), nil)
		return
	}
	if invoker.needLogin && !message.GetPlayerContext().IsLogin() {
		message.OnException(fmt.Errorf("need login"), nil)
		return
	}
	callArguments := make([]reflect.Value, 3)
	callArguments[0] = reflect.ValueOf(invoker.invoker)
	callArguments[1] = reflect.ValueOf(message.GetPlayerContext())
	callArguments[2] = reflect.ValueOf(structValue.Interface())
	defer func() {
		if err := recover(); err != nil {
			//TODO err的类型怎么限定
			message.OnException(fmt.Errorf("请求处理失败%v", err), request)
			zap.L().Error("请求处理失败", zap.Int32("cmd", message.GetCmd()), zap.Any("request", request), zap.Any("error", err))
		}
	}()
	values := invoker.method.Func.Call(callArguments)
	if len(values) > 0 {
		message.OnResponse(values[0].Interface())
	}
}

/**
 *  ProtoTag用例
 * 	type XxxxHandler struct {
 *		Proto_MethodA func() `proto:"method=MethodA,needLogin=false,enum=pb.ProtobufStructA.Proto,enumName=ID"`
 *		Proto_MethodB func() `proto:"method=MethodB,needLogin=false,enum=pb.ProtobufStructB.Proto,enumName=ID"`
 *	}
 *  func (this *XxxxHandler) MethodA(ctx *Ctx, protobufStruct *ProtobufStructA) {
 *		//do something
 *	}
 *  func (this *XxxxHandler) MethodB(ctx *Ctx, protobufStruct *ProtobufStructB) {
 *		//do something
 *	}
 */
type ProtoTag struct {
	method    string
	needLogin bool
	enum      string
	enumName  string
}

// 解释出proto标签
func parseProtoTag(tagDescriptor string) (protoTag ProtoTag) {
	protoTag.needLogin = true
	keyValues := strings.SplitN(tagDescriptor, ",", -1)
	for _, v := range keyValues {
		keyValue := strings.SplitN(v, "=", -1)
		switch keyValue[0] {
		case "method":
			protoTag.method = keyValue[1]
		case "needLogin":
			protoTag.needLogin, _ = strconv.ParseBool(keyValue[1])
		case "enum":
			protoTag.enum = keyValue[1]
		case "enumName":
			protoTag.enumName = keyValue[1]
		default:
		}
	}
	return
}
